# -*- coding: utf-8 -*-

import ast

from ir.ir_helper import ClassHelper
from ir.ir_helper import Helper
from ir.ir_ports import ITypeChannel, ITypeChannelPort, ITypeChannelPortSender, ITypeChannelPortReceiver

from .convert_lane import generate_lane_type, \
    create_lane_sender_helper, create_lane_receiver_helper, generate_lane_pack_type, create_packge_helper
from .helper_utils import create_class_helper


def generate_channel_type(channel_name: str, sender_ports, receiver_ports):
    # Based on the ports, generate the Sender and Receiver class definition.
    sender_type = ITypeChannelPortSender(channel_name)
    sender_type.ports = sender_ports
    receiver_type = ITypeChannelPortReceiver(channel_name)
    receiver_type.ports = receiver_ports
    # Complete.
    return sender_type, receiver_type


def create_channel_helper(port_type: ITypeChannelPort, parent_name: str, helper: Helper):
    # Generate a helper from parent type.
    port_helper = create_class_helper(parent_name, helper.chelper)
    # Need to modify the member data of the port.
    port_helper.member = port_type.ports
    return port_helper


def convert_channel(ast_channel: ast.ClassDef, helper: Helper):
    # Need to create multiple lanes for the Channel.
    channel_name = ast_channel.name
    # There should be only 1 assign as the body.
    ast_lane_map = None
    for ast_line in ast_channel.body:
        if isinstance(ast_line, ast.Assign) and len(ast_line.targets) == 1:
            # Check the target data.
            if isinstance(ast_line.targets[0], ast.Name) and \
                    ast_line.targets[0].id == 'lanes_map':
                # We find the lane
                ast_lane_map = ast_line.value
    if ast_lane_map is None:
        raise Exception('To define channel {}, you only need to assign value to lanes_map.'.format(channel_name))
    # Well it should be a dictionary.
    if not isinstance(ast_lane_map, ast.Dict):
        raise Exception('lanes_map should be a dictionary for channel {}.'.format(channel_name))
    # Prepare the ir list and helper list.
    ir_list = []
    helper_list = []
    # Prepare the port type for sender and receiver.
    sender_ports = {}
    receiver_ports = {}
    # Loop for all the keys, it should be the name of the lane.
    for ast_key, ast_value in zip(ast_lane_map.keys, ast_lane_map.values):
        # Each of them should be a string and a dictionary.
        if not isinstance(ast_key, ast.Str) or not isinstance(ast_value,
                                                              ast.Dict):
            raise Exception('Invalid lane definition in channel {}.'.format(channel_name))
        # Extract the lane name.
        lane_port_name = ast_key.s
        lane_type_name = 'Lane' + channel_name + lane_port_name
        # In the lane value definition, it should contain PORTS and GET.
        ast_ports = None
        ast_get_format = None
        is_reverse = False
        for ast_lane_key, ast_lane_value in zip(ast_value.keys, ast_value.values):
            # Each key should be an attribute.
            if not isinstance(ast_lane_key, ast.Attribute):
                raise Exception('Invalid lane definition key for Lane {} of Channel {}.'.format(ast_key.s, channel_name))
            # Check the attribute attr.
            attr_value = ast_lane_key.attr
            if attr_value == 'PORTS':
                ast_ports = ast_lane_value
            elif attr_value == 'GET':
                ast_get_format = ast_lane_value
            elif attr_value == 'REVERSE':
                is_reverse = ast_lane_value.value
        # Convert the lane type.
        lane_sender, lane_receiver = generate_lane_type(lane_type_name, ast_ports, ast_get_format, helper)
        pack_type = generate_lane_pack_type(lane_type_name, lane_receiver)
        # Add to ir list.
        ir_list += [lane_sender, lane_receiver, pack_type]
        helper_list += [create_lane_sender_helper(lane_sender, helper),
                        create_lane_receiver_helper(lane_receiver, pack_type, helper),
                        create_packge_helper(pack_type)]
        if is_reverse:
            sender_ports[lane_port_name] = lane_receiver
            receiver_ports[lane_port_name] = lane_sender
        else:
            sender_ports[lane_port_name] = lane_sender
            receiver_ports[lane_port_name] = lane_receiver
    # Also, add a Channel type to helper.
    # Build the channel ports.
    sender_type, receiver_type = generate_channel_type(channel_name, sender_ports, receiver_ports)
    ir_list += [sender_type, receiver_type]
    # Create the helper for the sender and receiver type.
    helper_list += [create_channel_helper(sender_type, 'ChannelPortSender', helper),
                    create_channel_helper(receiver_type, 'ChannelPortReceiver', helper)]
    # Construct an empty channel type.
    ir_list.append(ITypeChannel(channel_name))
    # Create the helper.
    chelper = ClassHelper()
    chelper.base = 'Channel'
    # Check whether base exist in define table.
    if chelper.base in helper.chelper:
        # Copy the function and member.
        chelper.func = helper.chelper[chelper.base].func.copy()
        chelper.member = helper.chelper[chelper.base].member.copy()
    helper_list.append(chelper)
    return ir_list, helper_list
